import socket
import socket
import sys
import threading
import time

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QTableWidgetItem, QCheckBox, QHBoxLayout, QWidget, QSpinBox

from myframe import *

version = "SimulateRfidAnt-V3.1"

"""
TODO: 将每个天线作为一个单独的socket响应查询命令

V3.1
增加支持广播命令识别与回复
增加启用和统一设置按钮更新卡号步进

V3.0
将原先的功能转移到美化版工程底版中
增加监听端口修改功能

V2.3
table添加一列存储秒级时间戳int(time.time())，并根据上次时间戳和刷卡间隔判断是回复刷到了所有卡，还是没刷到卡，刷到所有卡后更新时间戳，
根据独立的复位时间戳，定时回复一包职员卡数据
完善所有天线一键在线/离线功能
完善table响应次数的更新
V2.2
初步完成了按设定卡数量自动应答平均卡号
start按钮函数功能转移给统一设置按钮，同时socket改为首次点击start按钮才开启，后续点击start按钮只翻转enable变量
增加表格中模拟卡数量更改的handle函数，用于立刻刷新卡号步进和天线已使用卡号总数量
V2.1
完成了天线crc16校验函数

# TODO: 配置和table内容保存为ini或者其他文件，打开软件时找到就读取，否则就创建写默认值
"""


def dec_to_bcd(decimal):
    barr = bytearray(b'\x00\x00')
    if decimal <= 9999:
        dec_t = decimal//100
        barr[0] = ((dec_t//10) << 4)+(dec_t % 10)
        dec_t = decimal % 100
        barr[1] = ((dec_t//10) << 4)+(dec_t % 10)
    return barr


def calc_crc16(datas):
    crc16 = 0xFFFF
    poly = 0x8408
    for i in range(datas[0]-1):
        a = datas[i]
        crc16 = a ^ crc16  # ^ 异或运算：如果两个位为“异”（值不同），则该位结果为1，否则为0。
        for j in range(8):  # 对于每一个data，都需要右移8次，可以简单理解为对每一位都完成了校验
            if 1 & crc16 == 1:  # crc16与上1 的结果(16位二进制)只有第0位是1或0，其他位都是0    & 与运算：都是1才是1，否则为0
                crc16 = crc16 >> 1  # >>表示右移，即从高位向低位移出，最高位补0
                crc16 = crc16 ^ poly
            else:
                crc16 = crc16 >> 1
    # print(crc16)#得到的结果还是10进制
    barr = bytearray(2)
    barr[0] = crc16 & 0xff
    barr[1] = crc16 >> 8
    return barr


def checkout_crc16(datas):
    barr = calc_crc16(datas)
    if barr[0] == datas[-2] and barr[1] == datas[-1]:
        return 1
    else:
        return 0


class UserWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form_main()
        self.ui.setupUi(self)
        self.ui.pushButton_close.clicked.connect(self.close)
        # noinspection PyUnresolvedReferences
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)  # 设置窗口背景透明
        # noinspection PyUnresolvedReferences
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)  # 隐藏边框
        # noinspection PyArgumentList
        self.ui.frame.setGraphicsEffect(QtWidgets.QGraphicsDropShadowEffect(blurRadius=25, xOffset=3, yOffset=3))
        self.mos = 0
        # ====================
        self.ui.label_version.setText(version)
        # self.ui.tableWidget_ant.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(0, 50)  # 指定列宽与上方的自动铺满二选一生效
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(1, 79)
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(2, 89)
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(3, 125)
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(4, 1)
        self.ui.tableWidget_ant.horizontalHeader().resizeSection(5, 1)
        self.show()
        self.ui.checkBox_start.stateChanged.connect(self.handle_start)
        self.ui.pushButton.clicked.connect(self.handle_setallrfid)
        # self.client_socket = None
        # self.udp_client_th = None
        self.flag_th_run = 1
        self.socket_udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket_udp_server.settimeout(0.1)
        self.socket_udp_server.bind(('0.0.0.0', self.ui.spinBox_localport.value()))
        self.udp_client_th = threading.Thread(target=self.udp_client_recv)  # 用于创建一个线程持续监听UDP通信
        self.udp_client_th.start()
        self.flag_start = 2
        self.rfid_cnt = 0
        self.rfid_step = 0
        self.ant_valid_cnt = 0
        self.ui.checkBox_all.stateChanged.connect(self.handle_checkbox_all)
        self.rfid_rst_tick = 0
        self.ui.spinBox_localport.valueChanged.connect(self.hand_changeport)

    def closeEvent(self, event):  # 右上角关闭按钮 添加自定义逻辑
        self.flag_th_run = 0
        while 2 != self.flag_th_run:
            continue
        event.accept()  # 直到self.flag_th_run变为2，表示线程已经关闭，后面再执行关闭操作

    def set_table_item(self, row, col, data):
        # print(f'set_table_item  {row} {col} {data}')
        item_tmp = QTableWidgetItem(str(data))
        item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.ui.tableWidget_ant.setItem(row, col, item_tmp)

    def hand_changeport(self):
        self.socket_udp_server.close()
        self.socket_udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket_udp_server.settimeout(0.1)
        self.socket_udp_server.bind(('0.0.0.0', self.ui.spinBox_localport.value()))

    def handle_checkbox_all(self):
        if self.ui.checkBox_all.isChecked():
            self.ui.checkBox_all.setText("所有天线在线")
            for i in range(self.ui.tableWidget_ant.rowCount()):
                self.ui.tableWidget_ant.cellWidget(i, 0).checkbox.setChecked(True)
        else:
            self.ui.checkBox_all.setText("所有天线离线")
            for i in range(self.ui.tableWidget_ant.rowCount()):
                self.ui.tableWidget_ant.cellWidget(i, 0).checkbox.setChecked(False)

    def udp_client_recv(self):
        while 1 == self.flag_th_run:
            try:
                recv_msg, recv_addr = self.socket_udp_server.recvfrom(100)  # 最大报文长度 100字节
                print(recv_msg.hex())
                """
                V3.1
                # 主机广播查询所有天线，发送0x01指令   查询所有天线 04 ff 01 1b b4
                旧8dB天线 回复 Len  Adr	reCmd	Status	Num	EPC_ID	CRC-16
                            [0    [1     [2       [3    [4  [5 [6 [7 [8 [9        
                            06    01     01       01    00  crc1 crc2
                            0D    01     01       01    01  06 A0 22 20 00 00 36 crc1 crc2
                            14    01     01       01    02  06 A0 22 20 00 00 36 06 A0 22 20 00 00 47 crc1 crc2
                
                V3.0
                # 主机逐个查询天线，发送0x0F指令  查询1号天线 04 01 0F 7D BB     查询2号天线 04 02 0F 15 91   查询11号天线 04 OB OF OD 46    
                # 软件回复  1号天线无卡 05 01 OF FB 3E FD   1号天线刷到1张卡      0D 01 0F 01 01 06 A0 22 20 00 00 36 11 54
                #         11号天线无卡 05 OB OF FB 44 8E  11号天线刷到1张卡      OD OB OF 01 01 06 A0 22 20 00 00 36 99 43
                #                   11号天线刷到1张长卡号的卡  13 0B 0F 01 01 0C E2 80 68 92 02 40 50 7F 41 F2 53 13 0B 10
                
                """
                if 1 == self.flag_start and recv_msg[0] < 100 and checkout_crc16(recv_msg):
                    print('get msg')
                    if 0xff == recv_msg[1]:
                        ran_start = 0
                        ran_end = self.ui.spinBox_AntNum.value()
                    else:
                        ran_start = recv_msg[1] - 1
                        ran_end = recv_msg[1]
                    for idx_tmp in range(ran_start, ran_end):
                        if idx_tmp < self.ui.spinBox_AntNum.value() and self.ui.tableWidget_ant.cellWidget(idx_tmp, 0).checkbox.isChecked():
                            # 全局定时刷到职员卡
                            if self.ui.checkBox_autorst.isChecked() and int(time.time()) > (self.rfid_rst_tick + self.ui.spinBox_TimeRst.value()):
                                self.rfid_rst_tick = int(time.time())
                                send_arr = bytearray(b'\x0D\x01\x0F\x01\x01\x06\xB0\x22\x20\x00\x00\x01\x11\x54')
                                tmp_arr = dec_to_bcd(self.ui.spinBox_PoNum.value())
                                send_arr[1] = idx_tmp+1
                                send_arr[7] = tmp_arr[0]
                                send_arr[8] = tmp_arr[1]
                                barr = calc_crc16(send_arr)
                                send_arr[-2] = barr[0]
                                send_arr[-1] = barr[1]
                                self.socket_udp_server.sendto(send_arr, recv_addr)
                            # 正常回复给每个天线的刷卡命令
                            num_rfid_t = self.ui.tableWidget_ant.cellWidget(idx_tmp, 2).spinbox.value()
                            if num_rfid_t > 0:
                                self.set_table_item(idx_tmp, 3, int(self.ui.tableWidget_ant.item(idx_tmp, 3).text()) + 1)
                                self.ui.tableWidget_ant.viewport().update()
                                if int(time.time()) > int(self.ui.tableWidget_ant.item(idx_tmp, 5).text()):
                                    self.set_table_item(idx_tmp, 5, int(time.time()) + self.ui.spinBox_TimeRfid.value())
                                    self.ui.tableWidget_ant.viewport().update()
                                    for i in range(num_rfid_t, 0, -1):  # 有几张模拟卡，就循环几次
                                        send_arr = bytearray(b'\x0D\x01\x01\x01\x01\x06\xA0\x22\x20\x00\x00\x36\x11\x54')
                                        rfid_id = self.rfid_step * (int(self.ui.tableWidget_ant.item(idx_tmp, 4).text()) - i) + 1  # 循环倒序生成卡号
                                        print(f'i {i}  num_rfid_t {num_rfid_t} rfid_id {rfid_id}')
                                        tmp_arr = dec_to_bcd(self.ui.spinBox_PoNum.value())
                                        send_arr[1] = idx_tmp+1
                                        send_arr[7] = tmp_arr[0]
                                        send_arr[8] = tmp_arr[1]
                                        tmp_arr = dec_to_bcd(rfid_id)
                                        send_arr[10] = tmp_arr[0]
                                        send_arr[11] = tmp_arr[1]
                                        barr = calc_crc16(send_arr)
                                        send_arr[-2] = barr[0]
                                        send_arr[-1] = barr[1]
                                        self.socket_udp_server.sendto(send_arr, recv_addr)
                            else:
                                # 启用了天线，但设置的卡数量为0，回复无卡
                                send_arr = bytearray(b'\x05\x01\x0f\xfb\x00\x00')
                                barr = calc_crc16(send_arr)
                                send_arr[1] = idx_tmp+1
                                send_arr[-2] = barr[0]
                                send_arr[-1] = barr[1]
                                self.socket_udp_server.sendto(send_arr, recv_addr)

            except Exception as reason:
                pass
        print("rx over")
        self.flag_th_run = 2

    def handle_reflushrfid(self):
        # 启用的天线且模拟卡数量非0,计算模拟起始卡号和有效天线数量，否则模拟起始卡号为0  最终计算步进
        self.rfid_cnt = 0  # 逐个累加已使用的卡号数量
        for row in range(self.ui.tableWidget_ant.rowCount()):
            tmp_rfid_num = self.ui.tableWidget_ant.cellWidget(row, 2).spinbox.value()
            if self.ui.tableWidget_ant.cellWidget(row, 0).checkbox.isChecked() and tmp_rfid_num > 0:
                self.rfid_cnt += tmp_rfid_num
                item_tmp = QTableWidgetItem(str(self.rfid_cnt))
            else:
                item_tmp = QTableWidgetItem(str(0))
            item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.ui.tableWidget_ant.setItem(row, 4, item_tmp)
        if self.rfid_cnt > 1:
            self.rfid_step = (self.ui.spinBox_RfidMax.value() - 1) // (self.rfid_cnt - 1)
        else:
            self.rfid_step = 1
        print(f'step {self.rfid_step} rfid_cnt {self.rfid_cnt}')

    def handle_setallrfid(self):
        # 设置全部模拟天线为相同配置
        for i in range(self.ui.tableWidget_ant.rowCount()):
            self.ui.tableWidget_ant.removeRow(0)  # 清空重制
        for i in range(self.ui.spinBox_AntNum.value()):
            row = self.ui.tableWidget_ant.rowCount()
            self.ui.tableWidget_ant.insertRow(row)
            # 第一列 checkbox 并居中
            widget = QWidget()
            widget.checkbox = QCheckBox()  # 将checkbox放在widget中
            widget.checkbox.setCheckState(Qt.Checked)  # 默认全部勾选
            # noinspection PyUnresolvedReferences
            widget.checkbox.stateChanged.connect(self.handle_reflushrfid)
            t_layout = QHBoxLayout(widget)
            t_layout.addWidget(widget.checkbox)  # 为小部件添加checkbox属性
            t_layout.setAlignment(Qt.AlignCenter)  # 设置小控件水平居中
            widget.setLayout(t_layout)  # 在QWidget放置布局
            self.ui.tableWidget_ant.setCellWidget(row, 0, widget)
            # 非居中
            # checkbox = QCheckBox()
            # checkbox.setCheckState(2)
            # self.ui.tableWidget_ant.setCellWidget(row, 0, checkbox)
            # 第二列 天线ID
            item_tmp = QTableWidgetItem("Ant" + str(i + 1))
            item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.ui.tableWidget_ant.setItem(row, 1, item_tmp)
            # 第三列 天线模拟读到多少张卡
            widget = QWidget()
            widget.spinbox = QSpinBox()  # 将checkbox放在widget中
            widget.spinbox.setValue(self.ui.spinBox_RfidNum.value())  # 默认全部勾选
            # noinspection PyUnresolvedReferences
            widget.spinbox.valueChanged.connect(self.handle_reflushrfid)
            t_layout = QHBoxLayout(widget)
            t_layout.addWidget(widget.spinbox)  # 为小部件添加checkbox属性
            t_layout.setAlignment(Qt.AlignCenter)  # 设置小控件水平居中
            widget.setLayout(t_layout)  # 在QWidget放置布局
            self.ui.tableWidget_ant.setCellWidget(row, 2, widget)
            # 非spinbox
            # item_tmp = QTableWidgetItem(str(self.ui.spinBox_RfidNum.value()))
            # item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            # self.ui.tableWidget_ant.setItem(row, 2, item_tmp)
            # 第四列 天线收到多少次读卡指令
            item_tmp = QTableWidgetItem(str(0))
            item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.ui.tableWidget_ant.setItem(row, 3, item_tmp)
            # 第五列 当前天线模拟的rfid起始卡号    #默认使用相同数量的模拟卡时可简单乘2+1，使用不同数量的模拟卡时，需要详细计算
            item_tmp = QTableWidgetItem(str((row + 1) * self.ui.spinBox_RfidNum.value()))
            item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.ui.tableWidget_ant.setItem(row, 4, item_tmp)
            # 第6列 当前天线下次回复卡号的秒级时间戳    每次回复卡号后，此项增加刷卡间隔秒数，每次收到查询刷卡，判断时间戳大于此值才允许回复卡号，否则回复无卡
            item_tmp = QTableWidgetItem(str(0))
            item_tmp.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.ui.tableWidget_ant.setItem(row, 5, item_tmp)
            # 自动行高度
            self.ui.tableWidget_ant.resizeRowToContents(row)
            self.handle_reflushrfid()

    def handle_start(self):
        if self.ui.checkBox_start.isChecked():
            print(f'handle_start 1|{self.flag_start}|')
            if 2 == self.flag_start:
                print(f'handle_start 2|{self.flag_start}|')
                self.handle_setallrfid()
            self.flag_start = 1
            self.handle_reflushrfid()
        else:
            print(f'handle_start 3|{self.flag_start}|')
            self.flag_start = 0

    def mousePressEvent(self, e):  # 鼠标事件，按下按键
        # noinspection PyUnresolvedReferences
        if e.buttons() == QtCore.Qt.LeftButton:
            try:
                self.mos = e.pos()
            except:
                pass

    def mouseMoveEvent(self, event):  # underMouse()语句可以获取鼠标当前是否悬浮在相应控件上，通过该语句可以控制界面拖动的生效范围，使其不在按钮之类需要鼠标操作的地方误触发。
        if self.ui.pushButton_close.underMouse():
            pass
        else:
            try:
                if event.buttons() == Qt.LeftButton and self.mos:
                    self.move(self.mapToGlobal(event.pos() - self.mos))
                event.accept()
            except:
                pass


if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = UserWindow()
    # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")  # 修改任务栏图标和标题图标相同
    # win.show()
    win.setFixedSize(win.width(), win.height())
    sys.exit(app.exec_())